#include "utils.h"
#include <QByteArray>
#include <QDateTime>
#include <QFile>
#include <QTextStream>
#include <QDir>
#include <QRegularExpression>
#include <QThread>
#include <QUuid>
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <memory>
#include <functional>
#include <QRegularExpression>
#include <QProcess>
#include <QSettings>
#include <QTextCodec>
#include <QEventLoop>
#include <QTimer>
#include <QStorageInfo>
#include "../backup-daemon/parsebackuplist.h"

#include "mylittleparse.h"
#include "mydefine.h"

QString SystemInfo::m_os;
QString SystemInfo::m_arch;
QString SystemInfo::m_archDetect;

QString Utils::m_sysRootPath = "/";

/**
 * @brief initSysRootPath, 根据应用程序路径推断系统根目录
 * @param qsAppPath，应用程序所在路径
 * @note
 *      本方法依赖于发布后应用程序会部署到目录/usr/bin中
 *      1. grub引导中根目录为/root
 *      2. livecd中根目录为/target
 *      3. 正常使用中根目录为/
 */
void Utils::initSysRootPath(const QString& qsAppPath)
{
    QString sysRootPath = qsAppPath;
    if (sysRootPath.contains(DEFAULT_APP_PATH)) {
        sysRootPath.replace(DEFAULT_APP_PATH, "/");
    } else {
        sysRootPath = "/";
    }

    m_sysRootPath = sysRootPath;
}

/**
 * @brief customMessageHandler，日志重定向句柄
 * @param type 日志类型，debug等
 * @param context 上下文
 * @param msg 日志信息
 */
void Utils::customMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    QString strMsg("");
    switch (type) {
    case QtDebugMsg:
        strMsg = QString("[Debug]");
        break;
    case QtWarningMsg:
        strMsg = QString("[Warning]");
        break;
    case QtCriticalMsg:
        strMsg = QString("[Critical]");
        break;
    case QtFatalMsg:
        strMsg = QString("[Fatal]");
        break;
    default:
        strMsg = QString("[Debug]");
        break;
    }

    // 设置输出信息格式
    QString strDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
    QString strMessage = strMsg + QString("DateTime:%1 ThreadId:%2 Message:%3 File:%4(%5)")
            .arg(strDateTime).arg(QString::number(quintptr(QThread::currentThreadId()))).arg(localMsg.constData()).arg(context.file).arg(context.line);

    std::cout << strMessage.toUtf8().data() << std::endl;

    // 输出信息至文件中（读写、追加形式）
    QString fileName = m_sysRootPath + PROC_LOG;
    fileName.replace("//", "/");
    QFile file(fileName);
    file.open(QIODevice::ReadWrite | QIODevice::Append);
    QTextStream stream(&file);
    stream << strMessage << END_LINE;
    stream.flush();
    file.close();
}

/**
 * @brief 文件锁，锁定应用程序
 * @param frontUid，锁定程序的用户id
 * @return 锁文件的文件描述符
 * @note 使用方式类似于QLockFile，后面时间充足时也可以自行封装一个独立类来处理文件锁
 *       这个文件锁的作用是，在关机时关机模块会自行校验/tmp/lock路径下的锁文件，如果存在则提醒用户xx程序在运行不允许关机等
 */
int Utils::lockProgram(int frontUid)
{
    QDir dir(LOCK_FILE_PATH);
    if (!dir.exists()) {
        dir.mkdir(LOCK_FILE_PATH);
        chmod(LOCK_FILE_PATH, S_IRWXU | S_IRWXG | S_IRWXO);
    }

    int lock_file_fd = ::open(LOCK_FILE, O_CREAT | O_RDWR, 0666);
    if (0 > lock_file_fd) {
        return -2;
    }
    fchmod(lock_file_fd, S_IRWXU | S_IRWXG | S_IRWXO);

    int lock_ret = flock(lock_file_fd, LOCK_EX | LOCK_NB);
    if (0 > lock_ret) {
        return -11;
    }

    ftruncate(lock_file_fd, 0);

    char write_string[PID_STRING_LEN] = { 0 };
    snprintf(write_string, PID_STRING_LEN, "%d\n%s\n", frontUid, BACKUP_CLI_NAME);
    write(lock_file_fd, write_string, strlen(write_string));
    fdatasync(lock_file_fd);
    return lock_file_fd;
}

/**
 * @brief 解除应用程序文件锁
 * @param lock_file_fd 锁文件的文件描述符
 * @return 0，解除成功；1，解除失败
 */
int Utils::unLockProgram(int lock_file_fd)
{
    int lock_ret = flock(lock_file_fd, LOCK_UN);
    if (lock_ret < 0) {
        qCritical("unlock fail!");
        return 1;
    }
    qDebug("unlock success!");
    rmLockFile();
    return 0;
}

/**
 * @brief 删除锁文件
 * @return bool
 */
bool Utils::rmLockFile()
{
    bool res = QFile::remove(LOCK_FILE);
    if (!res)
        qCritical() << QString("remove %s fail").arg(LOCK_FILE);
    return res;
}

/**
 * @brief 检查/etc/.bootinfo是否存在并可读，里面存放备份分区的UUID等信息
 * @return bool
 */
bool Utils::checkBootInfoExists()
{
    QString bootinfoPath = Utils::m_sysRootPath + BOOTINFO_PATH;
    bootinfoPath.replace("//", "/");
    QFile bootinfoFile(bootinfoPath);
    if (!bootinfoFile.exists()) {
        qCritical("%s is not exists!", qUtf8Printable(bootinfoPath));
        return false;
    }
    if (!bootinfoFile.open(QIODevice::ReadOnly)) {
        qCritical("%s file can't open!", qUtf8Printable(bootinfoPath));
        return false;
    }
    bootinfoFile.close();
    return true;
}

/**
 * @brief 是否有平板模式
 * @return bool
 */
bool Utils::isTablet()
{
    QString otaPath = Utils::m_sysRootPath + "/etc/apt/ota_version";
    otaPath.replace("//", "/");

    return QFile::exists(otaPath);
}

/**
 * @brief 获取备份分区的UUID
 * @return 备份分区的UUID
 */
QString Utils::getBackupPartitionUuid()
{
    QString bootinfoPath = Utils::m_sysRootPath + BOOTINFO_PATH;
    bootinfoPath.replace("//", "/");

    MyLittleParse parse(bootinfoPath);
    QString restoreUuid;
    parse.find("RECOVERY_DEV_UUID", restoreUuid);

    if (restoreUuid.isEmpty()) {
        QString fstab = Utils::m_sysRootPath + FSTAB_PATH;
        fstab.replace("//", "/");
        QFile file(fstab);
        if (file.exists()) {
            if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                QTextStream in(&file);
                QString preLine;
                while (!in.atEnd()) {
                    QString line = in.readLine();
                    if (line.isEmpty())
                        continue;
                    if (line.startsWith("#")) {
                        preLine = line;
                        continue;
                    }
                    if (line.startsWith("UUID=") && line.contains("/backup")) {
                        // like:
                        // # /dev/add4 LABEL=KYLIN-BACKUP
                        // UUID=40be1cac-cd92-49db-8a98-68ee21ddbc49  /backup ext4 noauto  0  0
                        int indexOfSpace = line.indexOf(QRegularExpression("[ \t]+"), 0);
                        QString uuid = line.mid(0, indexOfSpace);
                        uuid.replace("UUID=", "");
                        restoreUuid = uuid.trimmed();

                        parse.Add("RECOVERY_DEV_UUID", restoreUuid);

                        break ;
                    } else if (line.startsWith("/dev/") && line.contains("/backup")) {
                        // like:
                        // # UUID=40be1cac-cd92-49db-8a98-68ee21ddbc49 LABEL=KYLIN-BACKUP
                        // /dev/add4  /backup ext4 noauto  0  0
                        if (preLine.startsWith("#") && preLine.contains("UUID=")) {
                            preLine.replace("# ", "");
                            preLine.replace("#", "");

                            int indexOfSpace = preLine.indexOf(QRegularExpression("[ \t]+"), 0);
                            QString uuid = preLine.mid(0, indexOfSpace);
                            uuid.replace("UUID=", "");
                            restoreUuid = uuid.trimmed();

                            parse.Add("RECOVERY_DEV_UUID", restoreUuid);

                            break ;
                        }
                    }
                }
            }
        }
    }

    return restoreUuid;
}

/**
 * @brief 获取分区对应的UUID映射关系
 * @param fstab文件路径
 * @return “分区挂载目录-分区UUID”键值对
 */
QHash<QString, QString> Utils::getPartUuidMap(const QString &fstab)
{
    QHash<QString, QString> result;
    QFile file(fstab);
    if (file.exists()) {
        if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&file);
            QString preLine;
            while (!in.atEnd()) {
                QString line = in.readLine();
                if (line.isEmpty())
                    continue;
                if (line.startsWith("#")) {
                    preLine = line;
                    continue;
                }

                // 配置文件/etc/fstab每行6个域: <file system> <mount point>   <type>  <options>       <dump>  <pass>, 形如:
                // UUID=232f5fb4-53e0-46b9-ba9b-22bfec64f2a2	/boot     	ext4      	rw,relatime	0 0
                QStringList list = line.split(QRegularExpression("[ \t]+"));
                QStringList fields;
                for (int i = 0; i < list.size(); ++i) {
                    QString field = list.at(i);
                    field = field.trimmed();
                    if (field.isEmpty())
                        continue;
                    fields << field;
                }
                QString mountPonit = fields.at(1);

                if (line.startsWith("UUID=")) {
                    // like:
                    // # /dev/add4 LABEL=KYLIN-BACKUP
                    // UUID=40be1cac-cd92-49db-8a98-68ee21ddbc49  /backup ext4 noauto  0  0
                    int indexOfSpace = line.indexOf(QRegularExpression("[ \t]+"), 0);
                    QString uuid = line.mid(0, indexOfSpace);
                    uuid.replace("UUID=", "");
                    uuid = uuid.trimmed();

                    result.insert(mountPonit, uuid);
                } else if (line.startsWith("/dev/")) {
                    // like:
                    // # UUID=40be1cac-cd92-49db-8a98-68ee21ddbc49 LABEL=KYLIN-BACKUP
                    // /dev/add4  /backup ext4 noauto  0  0
                    if (preLine.startsWith("#") && preLine.contains("UUID=")) {
                        preLine.replace("# ", "");
                        preLine.replace("#", "");

                        int indexOfSpace = preLine.indexOf(QRegularExpression("[ \t]+"), 0);
                        QString uuid = preLine.mid(0, indexOfSpace);
                        uuid.replace("UUID=", "");
                        uuid = uuid.trimmed();

                        result.insert(mountPonit, uuid);
                    }
                }
            }
        }
    }

    return result;
}

/**
 * @brief 路径不存在则创建，不支持递归创建
 * @param path
 */
void Utils::mkdir(const QString& path)
{
    QDir dir(path);
    if (!dir.exists())
        dir.mkdir(path);
}

/**
 * @brief 创建路径，支持递归创建
 * @param path
 * @return bool
 */
bool Utils::mkpath(const QString& path)
{
    QDir dir(path);
    if (!dir.exists()) {
        return dir.mkpath(path);
    }
    return true;
}

/**
 * @brief 备份还原时，需要排除绑定挂载的路径，因为数据不是存放在挂载路径内
 * @param excludes，存放需要排除的绑定路径
 */
void Utils::excludeFstabBindPath(QStringList &excludes)
{
    QString fstabPath = Utils::m_sysRootPath + FSTAB_PATH;
    fstabPath.replace("//", "/");

    QFile file(fstabPath);
    if (!file.open(QIODevice::ReadOnly))
        return;
    QTextStream in(&file);
    while (!in.atEnd()) {
       const QString &line = in.readLine();
       if (line.startsWith("#"))
           continue ;
       if (line.startsWith("UUID="))
           continue ;
       if (line.startsWith("/dev/"))
           continue ;
       if (line.isEmpty())
           continue ;
       if (!line.contains("bind"))
           continue ;

       // 配置文件/etc/fstab每行6个域: <file system> <mount point>   <type>  <options>       <dump>  <pass>, 形如:
       // UUID=232f5fb4-53e0-46b9-ba9b-22bfec64f2a2	/boot     	ext4      	rw,relatime	0 0
       QStringList list = line.split(QRegularExpression("[ \t]+"));
       QStringList fields;
       for (int i = 0; i < list.size(); ++i) {
          QString field = list.at(i);
          field = field.trimmed();
          if (field.isEmpty())
              continue;
          fields << field;
       }
       // 配置文件/etc/fstab每行6个域，第二个域为挂载路径, 为统一路径使用挂接点，排除第一个域
       if (6 == fields.size())
           excludes << fields.at(0);
    }

    file.close();
}

/**
 * @brief 备份还原时，需要一些用户家目录下的路径，因为这些路径备份还原程序无权操作
 * @param excludes，存放需要排除的路径
 */
void Utils::excludeSomeHomePath(QStringList &excludes)
{
    QSet<QString> homeSet;
    // 获取/home下的用户家目录
    QString homePath = Utils::m_sysRootPath + "/home";
    homePath.replace("//", "/");
    QDir dir(homePath);
    QStringList list = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs);
    for (const QString& item : list) {
        // /home/oem跳过
        if (item == "oem")
            continue ;

        QString home("/home/");
        home += item;
        excludes << home + "/.box";
        excludes << home + "/.log";
        QString dataHome("/data/home/");
        dataHome += item;
        excludes << dataHome + "/.box";
        excludes << dataHome + "/.log";
    }
}

/**
 * @brief 排除自定义备份路径
 * @param excludes，存放需要排除的路径
 */
void Utils::excludeCustomizePath(QStringList &excludes)
{
    // 本地xml文件中的信息
    QString xmlPath = Utils::getSysRootPath() + BACKUP_XML_PATH;
    xmlPath.replace("//", "/");
    ParseBackupList parse(xmlPath);
    parse.getCustomizePaths(excludes);
}

/**
 * @brief 生成rsync --exclude-from排除路径规则文件
 * @return
 */
bool Utils::generateExcludePathsFile()
{
    QString excludeFile = Utils::m_sysRootPath + EXCLUDE_FILE_PATH;
    excludeFile.replace("//", "/");

    QFile excludePathFile(excludeFile);
    // 暂时改为每次都重写文件内容，以便能随版本更新排除路径
    if (!excludePathFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        qCritical("%s create failed", qUtf8Printable(excludeFile));
        return false;
    }

    QTextStream in(&excludePathFile);
    in << "/backup" << END_LINE; //分区
    // in << "/boot/efi" << END_LINE;
    in << "/cdrom" << END_LINE;
    in << "/dev" << END_LINE;
    // efi原始目录在/boot/efi，备份到目标目录为/efi下，再还原时已经单独处理了，批量还原时应该屏蔽此目录
    in << "/efi" << END_LINE;
    // 安全模块会将文件/usr/share/kysec-utils/data/readonly_list中的文件列表限制只读，无法修改、备份（包含扩展属性时）、删除等
    // 现在里面仅有/etc/uid_list，先暂时排除掉；等后续安全模块有其它保护方案后再进一步修改
    in << "/etc/uid_list" << END_LINE;
    in << "/data/ghost" << END_LINE; //ghost镜像文件
    in << "/ghost" << END_LINE; //ghost镜像文件
    in << "/lost+found" << END_LINE;
    in << "/media" << END_LINE;
    in << "/mnt" << END_LINE;
    in << "/proc" << END_LINE;
    in << "/run" << END_LINE;
    in << "/swap_file" << END_LINE;
    in << "/sys" << END_LINE; //添加*(/sys/*)，表示如果/sys目录不存在，则会拷贝/sys，但不会拷贝/sys下的内容
    in << "/tmp" << END_LINE;
    in << "/var/lib/docker/overlay2" << END_LINE;
    // 安卓兼容的这个里面很多文件都是设置了特殊扩展文件属性，lsetxattr无法设置成功，听取安卓兼容模块同事的意见不用管这个文件夹，其实是从home下挂载的
    in << "/var/lib/kmre/data" << END_LINE;
    in << "/var/lib/kmre/kmre-*-*/data/media/0/0-麒麟*" << END_LINE;
    in << "/var/lib/udisks2" << END_LINE;
    in << "/var/log" << END_LINE;
    in << "*/backup/snapshots" << END_LINE;

    // 系统安装后有的会将/data/home /data/root挂载到的/home /root上，实际文件是存放在/data/home /data/root下面，为了统一标准保留/home /root排除/data/home /data/root
    QStringList excludes;
    Utils::excludeFstabBindPath(excludes);
    Utils::excludeSomeHomePath(excludes);
    Utils::excludeCustomizePath(excludes);
    for (const QString& item : excludes) {
        in << item << END_LINE;
    }
    in.flush();

    excludePathFile.close();
    return true;
}

/**
 * @brief 判断目录是否存在
 * @param 目录路径
 * @return true,存在；false,不存在
 */
bool Utils::isDirExist(const QString& fullDirName)
{
    QDir dir(fullDirName);
    if (dir.exists())
        return true;
    return false;
}

/**
 * @brief 获取系统备份还原排除路径列表, rsync --exclude-from排除路径规则文件中读取
 * @return 系统备份还原排除路径列表
 */
QStringList Utils::getFromExcludePathsFile()
{
        QStringList list;
        list << "/backup";
        // list << "/boot/efi";
        list << "/cdrom";
        list << "/dev";
        list << "/efi";
        list << "/etc/uid_list";
        list << "/data/ghost";
        list << "/ghost";
        list << "/lost+found";
        list << "/media";
        list << "/mnt";
        list << "/proc";
        list << "/run";
        list << "/swap_file";
        list << "/sys";
        list << "/tmp";
        list << "/var/lib/docker/overlay2";
        list << "/var/lib/kmre/data";
        list << "/var/lib/kmre/kmre-*-*/data/media/0/0-麒麟*";
        list << "/var/lib/udisks2";
        list << "/var/log";
        list << "*/backup/snapshots";

        // 系统安装后有的会将/data/home /data/root挂载到的/home /root上，实际文件是存放在/data/home /data/root下面
        QStringList excludes;
        Utils::excludeFstabBindPath(excludes);
        // Utils::excludeSomeHomePath(excludes);
        Utils::excludeCustomizePath(excludes);
        for (const QString& item : excludes) {
            list << item;
        }

        return list;
}

/**
 * @brief 生成Uuid
 * @return UUID
 */
QString Utils::createUuid()
{
    QString uuid;
    do {
        uuid = QUuid::createUuid().toString();
    } while (uuid == AUTO_BACKUP_UUID || uuid == FACTORY_BACKUP_UUID);
    return uuid;
}

/**
 * @brief 将列表中内容写入指定文件中
 * @param fileName，文件全路径
 * @param lines，内容列表
 * @return true，成功写入；false，写入失败
 */
bool Utils::writeFileByLines(const QString& fileName, const QStringList& lines)
{
    QFile file(fileName);
    if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
        return false;
    }

    QTextStream out(&file);
    for (const QString& line : lines) {
        out << line << END_LINE;
    }
    out.flush();
    file.close();
    return true;
}

/**
 * @brief 判断文件是否存在
 * @param fileName 文件明
 * @return bool，true-存在；false-不存在
 * @author zhaominyong
 * @since 2021/05/29
 */
bool Utils::filsExists(const QString &fileName)
{
    struct stat buffer;
    return (stat(fileName.toStdString().data(), &buffer) == 0);
}

/**
 * @brief 判断目录是否为空
 * @param fullDirName 目录路径
 * @return true-目录不存在或为空目录; false-非空目录
 */
bool Utils::isDirEmpty(const QString& fullDirName)
{
    QDir dir(fullDirName);
    if (!dir.exists())
        return true;

    if (dir.isEmpty())
        return true;

    return false;
}

/**
 * @brief 记录备份日志
 * @param line，日志内容
 * @return bool, true-成功；false-失败
 * @author zhaominyong
 * @since 2021/05/29
 * @note
 *      因为系统恢复成功后马上需要reboot，但是此时的文件缓存可能还未能落盘，故此增加此函数，其中调用了系统函数fdatasync确保缓存落盘
 */
bool Utils::writeBackupLog(QString line)
{
    line = line + "\n";
    QString logFile = Utils::getSysRootPath() + BACKUP_LOG_TEXT_PATH;
    logFile.replace("//", "/");
    std::string fileName(logFile.toStdString());
    // 判断文件是否存在
    bool exists = filsExists(logFile);

    std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(std::fopen(fileName.data(), "a+"), std::fclose);

    if (!fp) {
        std::perror("file opening failed!");
        return false;
    }

    if (!exists) {
        std::fputs("#This file must not be deleted and it is for the backup tool.\n", fp.get());
        std::fputs("#0:new system backup\n", fp.get());
        std::fputs("#1:update system backup\n", fp.get());
        std::fputs("#2:new data backup\n", fp.get());
        std::fputs("#3:update data backup\n", fp.get());
        std::fputs("#4:restore system\n", fp.get());
        std::fputs("#5:retaining user data\n", fp.get());
        std::fputs("#6:restore data\n", fp.get());
        std::fputs("#8:delete backup\n", fp.get());
        std::fputs("#for example: 17-04-25 10:43:56,{uuidxxxxx},0,this is a note,21.19KB,userid-1000,备份名称\n", fp.get());
    }
    std::fputs(line.toStdString().data(), fp.get());
    std::fflush(fp.get());
    fdatasync(fileno(fp.get()));

    return true;
}

/**
 * @brief 立即写文件
 * @param fileName 文件名，包含路径
 * @param content 文件内容
 * @return bool
 */
bool Utils::syncWriteFile(const QString &fileName, const QString& content)
{
    std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(std::fopen(fileName.toStdString().data(), "a+"), std::fclose);

    if (!fp) {
        std::perror("file opening failed!");
        return false;
    }

    QStringList lines = content.split("\n");
    for (const QString& line : lines) {
        std::fputs(line.toStdString().data(), fp.get());
    }
    std::fflush(fp.get());
    fdatasync(fileno(fp.get()));

    return true;
}

/**
 * @brief 将字节大小转换为GB等表示的字符串
 * @param size，qint64，空间大小，单位字节
 * @return GB/MB/KB等表示的字符串型大小
 */
QString Utils::StringBySize(qint64 size)
{
#define GB (1024 * 1024 * 1024)
#define MB (1024 * 1024)
#define KB (1024)

    float fcount = size * 1.0;

    if (size > GB)
        return QString(QString::number(fcount / GB, 10, 2) + "GB");
    else if (size > MB)
        return QString(QString::number(fcount / MB, 10, 2) + "MB");
    else
        return QString(QString::number(fcount / KB, 10, 2) + "KB");
}

/**
 * @brief 获取挂接的移动设备列表
 * @return MOUNTPOINT,PATH键值对列表
 * @author zhaominyong
 * @since 2021/06/07
 * @note
 *      for bug  59636 【cpm】【HUAWEI】【KOS】【L0】【V3试制】备份时选择移动设备，会出现一个dev/sdc的路径(一般+必现+不常用功能)
 *      QStorageInfo::mountedVolumes获取的磁盘列表区分不出来是否移动设备
 */
QHash<QString, QString> Utils::getRemovableStorages()
{
    QHash<QString, QString> removalbeStorages;

    QProcess process_lsblk;
    process_lsblk.start("lsblk -P -o PATH,RM,TYPE,MOUNTPOINT,TRAN");
    if (!process_lsblk.waitForStarted(-1)) {
        return removalbeStorages;
    }
    if (!process_lsblk.waitForFinished(-1)) {
        return removalbeStorages;
    }
    QString result = process_lsblk.readAllStandardOutput();
    /*
    result like bellow :
    PATH="/dev/sda" RM="0" TYPE="disk" MOUNTPOINT="" TRAN="sata"
    PATH="/dev/sda1" RM="0" TYPE="part" MOUNTPOINT="/media/zhaominyong/DATA1" TRAN=""
    PATH="/dev/sdb" RM="1" TYPE="disk" MOUNTPOINT=""  TRAN="usb"
    PATH="/dev/sdb4" RM="1" TYPE="part" MOUNTPOINT="/media/zhaominyong/31  GB"  TRAN=""
    PATH="/dev/sr0" RM="1" TYPE="rom" MOUNTPOINT="/media/zhaominyong/Kylin-Desktop-V10-SP1"  TRAN=""
    */
    QStringList items = result.split("\n");
    QStringList usbDevs;
    for (QStringList::const_iterator it = items.constBegin(); it != items.constEnd(); ++it) {
        const QString &line = (*it);
        if (line.contains(" TRAN=\"usb\"")) {
            QStringList storageAttrs = line.split("\" ");
            if (5 != storageAttrs.size())
                continue ;
            QString path = storageAttrs.at(0);
            path = path.replace("PATH=\"", "");
            usbDevs.append(path);
        }
        if (line.contains(" RM=\"1\" ") || line.contains(" MOUNTPOINT=\"/media/")) {
            if (line.contains(" TYPE=\"rom\" "))
                continue;
            if (line.contains(" MOUNTPOINT=\"\""))
                continue;

            // "PATH RM TYPE MOUNTPOINT TRAN" for each row, split by "\" "
            QStringList storageAttrs = line.split("\" ");
            if (5 != storageAttrs.size())
                continue ;
            QString path = storageAttrs.at(0);
            path = path.replace("PATH=\"", "");
            bool isSubPart = false;
            for (const QString& usbDev : usbDevs) {
                if (path.contains(usbDev)) {
                    isSubPart = true;
                    break;
                }
            }
            if (!isSubPart)
                continue;
            QString mount_point = storageAttrs.at(3);
            mount_point = mount_point.replace("MOUNTPOINT=\"", "");
            removalbeStorages.insert(path, mount_point);
        }
    }

    return removalbeStorages;
}

/**
 * @brief 获取挂接的计算机内部磁盘
 * @return 内部磁盘挂接路径列表
 */
QList<QString> Utils::getLocalDisks()
{
    QList<QString> localDisks;

    QProcess process_lsblk;
    process_lsblk.start("lsblk -P -o PATH,RM,TYPE,MOUNTPOINT,TRAN");
    if (!process_lsblk.waitForStarted(-1)) {
        return localDisks;
    }
    if (!process_lsblk.waitForFinished(-1)) {
        return localDisks;
    }
    QString result = process_lsblk.readAllStandardOutput();

    QString userName = qgetenv("USER");
    QString mountPointPre("/media/");
    mountPointPre += userName;
    /*
    result like bellow :
    PATH="/dev/sda" RM="0" TYPE="disk" MOUNTPOINT="" TRAN="sata"
    PATH="/dev/sda1" RM="0" TYPE="part" MOUNTPOINT="/media/zhaominyong/DATA1" TRAN=""
    PATH="/dev/sdb" RM="1" TYPE="disk" MOUNTPOINT=""  TRAN="usb"
    PATH="/dev/sdb4" RM="1" TYPE="part" MOUNTPOINT="/media/zhaominyong/31  GB"  TRAN=""
    PATH="/dev/sr0" RM="1" TYPE="rom" MOUNTPOINT="/media/zhaominyong/Kylin-Desktop-V10-SP1"  TRAN=""
    */
    QStringList items = result.split("\n");
    QStringList usbDevs;
    for (QStringList::const_iterator it = items.constBegin(); it != items.constEnd(); ++it) {
        const QString &line = (*it);
        if (line.contains(QRegularExpression(" TRAN=\".+\"")) && !line.contains(" TRAN=\"usb\"")) {
            QStringList storageAttrs = line.split("\" ");
            if (5 != storageAttrs.size())
                continue ;
            QString path = storageAttrs.at(0);
            path = path.replace("PATH=\"", "");
            usbDevs.append(path);
            continue;
        }
        if (line.contains(" TYPE=\"part\" ") && line.contains(mountPointPre)) {
            if (line.contains(" MOUNTPOINT=\"\""))
                continue;

            // "PATH RM TYPE MOUNTPOINT TRAN" for each row, split by "\" "
            QStringList storageAttrs = line.split("\" ");
            if (5 != storageAttrs.size())
                continue ;
            QString path = storageAttrs.at(0);
            path = path.replace("PATH=\"", "");
            bool isSubPart = false;
            for (const QString& usbDev : usbDevs) {
                if (path.contains(usbDev)) {
                    isSubPart = true;
                    break;
                }
            }
            if (!isSubPart)
                continue;

            // 默认挂载的内部磁盘不太可能使用中文字符等，暂不考虑挂载路径被转义的场景
            QString mount_point = storageAttrs.at(3);
            mount_point = mount_point.replace("MOUNTPOINT=\"", "");
            localDisks << mount_point;
        }
    }

    return localDisks;
}

/**
 * @brief 设置安全状态
 * @param enable——true，开启保护；false，关闭保护
 */
void Utils::setKysecStatus(bool enable)
{
    QFile file("/usr/bin/setstatus");
    if (!file.exists()) {
        file.setFileName("/usr/sbin/setstatus");
        if (!file.exists())
            return ;
    }

    if (enable) {
        QProcess::execute("setstatus enable");
    } else {
        QProcess::execute("setstatus softmode");
    }
}

/**
 * @brief 启动或关闭kysec-sync-daemon服务
 * @param enable——true，开启；false，关闭
 */
void Utils::setKysecDaemon(bool enable){
    if(enable){
        QProcess::execute("systemctl start kysec-sync-daemon");
    }else{
        QProcess::execute("systemctl stop kysec-sync-daemon");
    }
}

/**
 * @brief 判断程序是否已开启
 * @param processName
 * @return bool
 */
bool Utils::isRunning(const QString &processName)
{
    bool bRet = false;

    QProcess ps;
    QProcess grep;
    ps.setStandardOutputProcess(&grep);

    ps.start("ps", QStringList() << "-ef");
    grep.start("grep", QStringList() << processName);

    if (grep.waitForFinished()) {
        QString result(grep.readAll());
        qDebug() << result;
        QStringList lines = result.split("\n");
        for (const QString& line : lines) {
            if (line.contains("grep"))
                continue;
            if (line.contains(processName)){
                bRet = true;
                break;
            }
        }
    }

    grep.close();
    ps.close();

    return bRet;
}

/**
 * @brief 判断是否990或9006C、PANGU处理器
 * @return bool
 */
bool Utils::isHuawei990()
{
    QString result;
    Utils::executeCMD("cat /proc/cpuinfo | grep -i \"kirin.*9.0\"", result);
    if (result.isEmpty()) {
        Utils::executeCMD("cat /proc/cpuinfo | grep -i \"PANGU\"", result);
        if (result.isEmpty())
            return false;
    }

    return true;
}

/**
 * @brief popen()函数通过创建一个管道，调用fork()产生一个子进程，执行一个shell以运行命令来开启一个进程。
 * @param cmd，是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志，shell 将执行这个命令
 * @param result 输出结果
 * @note
 */
void Utils::executeCMD(const char* cmd, QString &result)
{
    char buf_ps[1024] = { 0 };
    FILE* ptr = nullptr;
    // 只能是读或者写中的一种，得到的返回值（标准 I/O 流）也具有和 type 相应的只读或只写类型。
    // 如果 type 是 “r” 则文件指针连接到 command 的标准输出；如果 type 是 “w” 则文件指针连接到 command 的标准输入。
    qDebug() << cmd;
    if ((ptr = popen(cmd, "r")) != nullptr) {
        while (fgets(buf_ps, 1024, ptr) != nullptr) {
            result += buf_ps;
            memset(buf_ps, 0, 1024);
        }
        pclose(ptr);
        ptr = nullptr;
    } else {
        qCritical("popen %s error", cmd);
    }
}

/**
 * @brief Utils::executeCmd
 * @param cmd
 * @param args
 * @return 输出
 */
QString Utils::executeCmd(const QString &cmd, const QStringList &args)
{
    QString cmdLine(cmd);
    for (const QString &arg : args) {
        cmdLine += " ";
        cmdLine += arg;
    }
    qDebug() << cmdLine;

    QString result;
    char buf_ps[1024] = { 0 };
    FILE* ptr = nullptr;
    // 只能是读或者写中的一种，得到的返回值（标准 I/O 流）也具有和 type 相应的只读或只写类型。
    // 如果 type 是 “r” 则文件指针连接到 command 的标准输出；如果 type 是 “w” 则文件指针连接到 command 的标准输入。
    if ((ptr = popen(cmdLine.toStdString().data(), "r")) != nullptr) {
        while (fgets(buf_ps, 1024, ptr) != nullptr) {
            result += buf_ps;
            memset(buf_ps, 0, 1024);
        }
        pclose(ptr);
        ptr = nullptr;
    } else {
        qCritical("popen %s error", cmdLine.toStdString().data());
    }
    return result;
}

/**
 * @brief 获取备份日志列表
 * @return
 */
QList<BackupWrapper> Utils::getBackupLogList()
{
    // 为兼容以前的老备份数据，在此处先获取uuid-name键值对
    QMap<QString, QString> uuid_name = Utils::getBackupUuidNameMap();

    QString logFile = Utils::getSysRootPath() + BACKUP_LOG_TEXT_PATH;
    logFile.replace("//", "/");
    QFile file(logFile);
    QList<BackupWrapper> list;
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        while (!in.atEnd()) {
            QString line = in.readLine();
            if (line.startsWith("#") || line.isEmpty())
                continue;
            QStringList fields = line.split(",");
            BackupWrapper record;
            int index = 0;
            for (const QString &field : fields) {
                switch (index) {
                case 0: // 备份时间
                    record.m_time = field;
                    break;
                case 1: // uuid
                    record.m_uuid = field;
                    break;
                case 2: // 操作类型
                    record.m_type = field.toInt();
                    break;
                case 3: // 备注
                    record.m_note = field;
                    break;
                case 4: // 备份大小
                    record.m_size = field;
                    break;
                case 5: // 备份用户id
                    if (field.isEmpty())
                        record.m_frontUid = -1;
                    else
                        record.m_frontUid = field.toInt();
                    break;
                case 6: // 备份名称
                    record.m_backupName = field;
                    break;
                default:
                    break;
                }

                ++index;
            }

            if (record.m_backupName.isEmpty()) {
                if (FACTORY_BACKUP_UUID == record.m_uuid) {
                    record.m_backupName = QObject::tr("Factory Backup");
                }
//                else if (AUTO_BACKUP_UUID == record.m_uuid) {
//                    record.m_backupName = QObject::tr("Auto Backup");
//                }
                else if (uuid_name.contains(record.m_uuid)) {
                    record.m_backupName = uuid_name.value(record.m_uuid);
                }
                else {
                    record.m_backupName = record.m_time;
                }
            } else if (FACTORY_BACKUP_UUID == record.m_uuid) {
                record.m_backupName = QObject::tr("Factory Backup");
            }

            list << record;
        }
    }

    return list;
}

/**
 * @brief 初始化系统信息
 */
void Utils::initSystemInfo()
{
    SystemInfo::m_os = Utils::getOs();
    SystemInfo::m_arch = Utils::getArch();
    SystemInfo::m_archDetect = Utils::getArchDetect();
}

/**
 * @brief getOs
 * @return 操作系统名字, 如：
 *         Kylin-Desktop V10-SP1
 *         Build 20210407
 */
QString Utils::getOs()
{
    QString path = Utils::getSysRootPath() + "/etc/kylin-build";
    path.replace("//", "/");
    QFile file(path);
    /* 文件内容形如：
    Kylin-Desktop V10-SP1
    Build 20210407
    */
    QString os;
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        if (!in.atEnd()) {
            os = in.readLine();
        }
    }

    return os;
}

/**
 * @brief getArch
 * @return 架构，arch命令的结果，如：x86_64
 */
QString Utils::getArch()
{
    QString arch = Utils::executeCmd("arch");
    return arch.replace("\n", "");
}

/**
 * @brief getArchDetect
 * @return archdetect命令的结果，如：amd64/efi
 */
QString Utils::getArchDetect()
{
    QString arch = Utils::executeCmd("archdetect");
    return arch.replace("\n", "");
}

/**
 * @brief 获取分区剩余大小
 * @return
 */
QHash<QString, QString> Utils::getLeftSizeOfPartitions()
{
    QString root = Utils::getSysRootPath();
    QString backup = Utils::getSysRootPath() + BACKUP_PATH;
    backup.replace("//", "/");
    QString data = Utils::getSysRootPath() + DATA_PATH;
    data.replace("//", "/");

    QStringList args;
    args << "-h";
    args << backup;
    args << data;
    args << root;

    QHash<QString, QString> hash;

    QProcess df;
    df.start("df", args);
    if (df.waitForFinished()) {
        QString result(df.readAll());
        QStringList lines = result.split("\n");
/*
文件系统        容量  已用  可用 已用% 挂载点
/dev/nvme0n1p4   26G   45M   25G    1% /backup
/dev/nvme0n1p3   98G   54G   40G   58% /
/dev/nvme0n1p5   90G  8.6G   77G   11% /data
*/
        // 排除第一行标题
        for (int i = 1; i < lines.size(); ++i) {
            QString line = lines.at(i);
            line = line.trimmed();
            if (line.isEmpty())
                continue;

            QStringList fields = line.split(QRegularExpression("[ \t]+"));
            if (fields.size() != 6)
                continue;
            QString path = fields.at(5);
            QString left = fields.at(3);
            hash.insert(path, left);
        }
    }
    return hash;
}

/**
 * @brief 获取分区可用大小，主要是/和/data分区
 * @return 路径和大小（单位：字节）键值对
 */
QHash<QString, qint64> Utils::getAvailableSizeOfPartitions()
{
    QHash<QString, qint64> hash;

    QString root = Utils::getSysRootPath();
    QStorageInfo rootInfo(root);
    hash.insert(root, rootInfo.bytesAvailable());

    QString data = Utils::getSysRootPath() + DATA_PATH;
    data.replace("//", "/");
    QStorageInfo dataInfo(data);
    hash.insert(data, dataInfo.bytesAvailable());

    return hash;
}

/**
 * @brief 获取文件夹或文件的大小
 * @param path 路径
 * @return 大小
 */
qint64 Utils::getDirOrFileSize(const QString &path)
{
    char cmd[1024] = { 0 };
    sprintf(cmd, "du -sb '%s' 2>/dev/null | awk '{print $1}'", path.toLocal8Bit().data()); //s:total k:1024 bytes b:1 byte

    QString qsSize;
    Utils::executeCMD(cmd, qsSize);
    qsSize.replace("\n", "");
    qsSize = qsSize.trimmed();
    return qsSize.toLongLong();
}

/**
 * @brief 记录下备份点uuid及其名称
 * @param uuid 备份点识别码
 * @param backupName 备份点名称
 */
void Utils::update_backup_unique_settings(const QString &uuid, const QString &backupName)
{
    // 由于历史原因（原来只校验U盘备份的唯一性）,文件名暂不改变
    QString backupUniqueSetting = Utils::getSysRootPath() + UDISK_UNIQUE_SETTINGS;
    backupUniqueSetting.replace("//", "/");
    QSettings udisk_unique_settings(backupUniqueSetting, QSettings::IniFormat);
    udisk_unique_settings.setIniCodec(QTextCodec::codecForName("utf-8"));
    udisk_unique_settings.beginGroup(backupName);
    udisk_unique_settings.setValue("uuid", uuid);
    udisk_unique_settings.endGroup();
}

/**
 * @brief 根据备份点名称，删除备份点uuid记录
 * @param backupName 备份点名称
 */
void Utils::deleteBackupUniqueRecord(const QString& backupName)
{
    QString backupUniqueSetting = Utils::getSysRootPath() + UDISK_UNIQUE_SETTINGS;
    backupUniqueSetting.replace("//", "/");
    QSettings udisk_unique_settings(backupUniqueSetting, QSettings::IniFormat);
    udisk_unique_settings.setIniCodec(QTextCodec::codecForName("utf-8"));
    udisk_unique_settings.remove(backupName);
}

/**
 * @brief 获取备份点Uuid-BackupName键值对
 * @return Uuid-BackupName键值对
 */
QMap<QString, QString> Utils::getBackupUuidNameMap()
{
    // 1、udisk_unique_file文件中的信息
    QString backupUniqueSetting = Utils::getSysRootPath() + UDISK_UNIQUE_SETTINGS;
    backupUniqueSetting.replace("//", "/");
    QSettings udisk_unique_settings(backupUniqueSetting, QSettings::IniFormat);
    QStringList groups = udisk_unique_settings.childGroups();

    QMap<QString, QString> result;
    for (const QString& group : groups) {
        udisk_unique_settings.beginGroup(group);
        QString uuid = udisk_unique_settings.value("uuid").toString();
        udisk_unique_settings.endGroup();
        result.insert(uuid, group);
    }

    // 2、本地xml文件中的信息
    QString xmlPath = Utils::getSysRootPath() + BACKUP_XML_PATH;
    xmlPath.replace("//", "/");
    ParseBackupList parse(xmlPath);
    parse.getXmlUuidNameMap(result);

    return result;
}

/**
 * @brief 前后两次调用，然后取文件修改时间用于判断缓存数据是否落盘
 * @return
 */
bool Utils::updateSyncFile()
{
    QString fileIfSync = Utils::getSysRootPath() + FILE_IF_SYNC;
    fileIfSync.replace("//", "/");
    QFile file(fileIfSync);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        return false;
    }

    QTextStream out(&file);
    out << "sync" << END_LINE;
    out.flush();
    file.flush();
    file.close();

    return true;
}

/**
 * @brief 用事件循环替换sleep，以便事件尽量得到处理
 * @param sec，等待时间，单位秒
 * @author zhaominyong
 * @since 2021/05/29
 */
void Utils::wait(uint sec)
{
    QEventLoop loop;
    QTimer::singleShot(1000 * sec, &loop, SLOT(quit()));
    loop.exec();
}


